package com.chris.core.socket.netty.util;

import com.chris.core.socket.netty.code.CustomMessageDecoder;
import com.chris.core.socket.netty.code.CustomMessageEncoder;
import com.chris.core.socket.netty.dto.SocketChannelMapDTO;
import com.chris.core.socket.netty.dto.SocketTypeEnum;

import com.chris.core.socket.netty.handler.heatbeat.HeatBeatCustomHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.chris.core.socket.netty.handler.heatbeat.HeatBeatObjectHandler;
import com.chris.core.socket.netty.handler.heatbeat.HeatBeatStringHandler;

import java.io.File;
import java.util.concurrent.TimeUnit;


/**
 * netty 通用类 服务端
 * 注意坑：
 *    ctx.writeAndFlush 是当前handler往前找encode, ctx.channel().writeAndFlush(dto) 从last找encode
 *    ObjectDecoder /ObjectEncoder 对象必须先序列化
 */
public abstract class NettyServerUtil {

    //#region 参数及变量
    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 端口
     */
    private int port;

    /**
     * 服务器读取超时时间(秒)
     */
    private int readTimeOut=180;


    /**
     * 消息断言
     */
    public  String breakMark = "\r\n";// \n    \r\n    ~!@#bbq8$%^&*\n;

    /**
     * 服务器类型
     * “” 普通类型  |  websocket
     */
    private SocketTypeEnum serverType=SocketTypeEnum.ByteSocket;

    /**
     * 主selector线程组
     * 主要监控atccpet
     * LoopGroup子线程的个数默认为： CPU 核数 * 2 ，可以手工指定
     */
    private EventLoopGroup boosGroup;

    /**
     * 主selector线程组
     * 主要监控 read / write 事件，再发起hadler 线程去处理
     * LoopGroup子线程的个数默认为： CPU 核数 * 2 ，可以手工指定
     */
    private EventLoopGroup workGroup;

    /**
     * 服务端启动对象
     */
    private ServerBootstrap bootstarp;






    //#endregion

    //#region 构造函数及其初始化

    /**
     * 构造函数
     * @param port 端口
     * @param readTimeOut 读取超时
     * @param breakMark 断言
     * @param serverType 服务类型
     */
       public NettyServerUtil(int port,int readTimeOut,String breakMark,SocketTypeEnum serverType) {
        this.serverType= serverType;
        this.readTimeOut=readTimeOut;
        this.breakMark=breakMark;
        this.port = port;
    }

    /**
     * 初始化系统参数
     */

    private void IntSystem() {

        try {
            //EventLoopGroup boosGroup = new NioEventLoopGroup(1);//开1线程
           // EventLoopGroup workGroup = new NioEventLoopGroup(32);//开2线程
            boosGroup = new NioEventLoopGroup(2);//开2线程
            workGroup = new NioEventLoopGroup(32);//开32线程
            bootstarp = new ServerBootstrap();

            bootstarp.group(boosGroup, workGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//   //使用NioSocketChannel作为服务器到通道实现
                    // 如果未设置或所设置的值小于1，Java将使用默认值50。
                    // 如果大于队列的最大长度，请求会被拒绝
                    // .childOption(ChannelOption.SO_KEEPALIVE, true)//changle保持链接状态
                    //添加自定义处理handler
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            // String uuid = UUID.randomUUID().toString();

                            //SocketChannelMapDTO dto = new SocketChannelMapDTO(uuid,ch);
                            //SocketChannelMap.put(uuid,dto);
                            //acccpet 获取 ch
                            // ch.pipeline().addLast(new NettyServerHandler());

                            //#region 服务端类型
                            if (serverType == SocketTypeEnum.ByteSocket) {
                                //#region -------------编解码---------------
                                //断言拆包pipline必须放在第一位，内容先做buff截留，拆包后才把内容out.add抛给下家decoder
                                if (breakMark != "") {
                                    ByteBuf delimiter = Unpooled.wrappedBuffer(breakMark.getBytes(CharsetUtil.UTF_8));
                                    pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE,
                                            delimiter));//, delimiter_1
                                }
                                //解码器
                                pipeline.addLast("decoder", new ByteArrayDecoder());

                                //编码器
                                pipeline.addLast("encoder", new ByteArrayEncoder());
                                //#endregion -------------内容编码------------

                                //#region -------------心跳---------------
                                //心跳策略：
                                // (1)客户端  检测写心跳 , 30s 没有内容自动发送ping , 不断开 , 服务端不回复 pong  ; 检测读 60s *3 心跳 , 没有内容动自动断开连接
                                // (2)服务端  检测写心跳 , 60s 没有内容自动发送ping , 不断开 , 客户端不回复 pong  ; 检测读 30s *6 心跳 , 没有内容动自动断开连接

                                //设置时间段内都没有数据读取了，那么就引发超时，然后关闭当前的channel --> 会自动断开
                                //pipeline.addLast(new ReadTimeoutHandler(readTimeOut));
                                //控制数据输出的时候的超时，如果在设置时间段内都没有数据写了，那么就超时--> 会自动断开
                                //pipeline.addLast(new WriteTimeoutHandler(30*60));
                                /**
                                 * 空闲状态处理器
                                 * readerIdleTime : 空闲 ？ 读取时间 ， 自动发送心跳检测包
                                 * writerIdleTime ：空闲 ？ 写时间， 自动发送心跳包
                                 * allIdleTime： 空闲 ？ 读+写时间，自动发送心跳包
                                 */
                                //pipeline.addLast(new IdleStateHandler(0, 60, 0, TimeUnit.SECONDS));
                                //重要：上下handler会互相关联，加入自定义心跳事件，对应上面函数IdleStateHandler-->会触发下面的handler-->HeatBeatHandler
                                //pipeline.addLast(new HeatBeatHandler());

                                //-------------------------------------------
                                //#endregion
                            }
                            else if (serverType == SocketTypeEnum.StringSocket) {

                                //#region -------------编解码---------------
                                //断言拆包pipline必须放在第一位，内容先做buff截留，拆包后才把内容out.add抛给下家decoder
                                if (breakMark!="") {
                                    //同时存在多个分隔符时，优先匹配长度最短的分隔符，如果一样长，则哪个先出现，匹配哪个。
                                    //FixedLengthFrameDecoder(20) 固定长度20
                                    //LineBasedFrameDecoder: 以换行符为结束标志的解码器
                                    //DelimiterBasedFrameDecoder： 指定消息分隔符的解码器


                                    ByteBuf delimiter = Unpooled.wrappedBuffer(breakMark.getBytes(CharsetUtil.UTF_8));
                                    // 将delimiter设置到DelimiterBasedFrameDecoder中，经过该解码一器进行处理之后，源数据将会
                                    // 被按照_$进行分隔，这里1024指的是分隔的最大长度，即当读取到1024（指定大小）个字节的数据之后，若还是未
                                    // 读取到分隔符，则舍弃当前数据段，因为其很有可能是由于码流紊乱造成的
                                    pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE,delimiter));
                                }

                                //解码器 文本转换为utf-8
                                pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
                                //编码器 文本转换为utf-8
                                pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));


                                //#endregion -------------内容编码------------

                                //#region -------------心跳---------------
                                //心跳策略：
                                // (1)客户端  检测写心跳 , 30s 没有内容自动发送ping , 不断开 , 服务端不回复 pong  ; 检测读 60s *3 心跳 , 没有内容动自动断开连接
                                // (2)服务端  检测写心跳 , 60s 没有内容自动发送ping , 不断开 , 客户端不回复 pong  ; 检测读 30s *6 心跳 , 没有内容动自动断开连接

                                //设置时间段内都没有数据读取了，那么就引发超时，然后关闭当前的channel --> 会自动断开
                                pipeline.addLast(new ReadTimeoutHandler(readTimeOut));
                                //控制数据输出的时候的超时，如果在设置时间段内都没有数据写了，那么就超时--> 会自动断开
                                //pipeline.addLast(new WriteTimeoutHandler(30*60));
                                /**
                                 * 空闲状态处理器
                                 * readerIdleTime : 空闲 ？ 读取时间 ， 自动发送心跳检测包
                                 * writerIdleTime ：空闲 ？ 写时间， 自动发送心跳包
                                 * allIdleTime： 空闲 ？ 读+写时间，自动发送心跳包
                                 */
                                pipeline.addLast(new IdleStateHandler(0, 60, 0, TimeUnit.SECONDS));
                                //重要：上下handler会互相关联，加入自定义心跳事件，对应上面函数IdleStateHandler-->会触发下面的handler-->HeatBeatHandler
                                pipeline.addLast(new HeatBeatStringHandler());

                                //-------------------------------------------
                                //#endregion


                            }
                            else if (serverType==SocketTypeEnum.WebSocket){//webSocket
                            }
                            else if (serverType==SocketTypeEnum.ObjectSocket) {
                                //#region -------------编解码---------------
                                //断言貌似ojetct方式会自动断言和设置大小
                                //解码器
                                pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));

                                //编码器
                                pipeline.addLast("encoder", new ObjectEncoder());

                                //#endregion



                                //#region -------------心跳---------------

                                // (1)客户端  检测写心跳 , 30s 没有内容自动发送ping , 不断开 , 服务端不回复 pong  ; 检测读 60s *3 心跳 , 没有内容动自动断开连接
                                // (2)服务端  检测写心跳 , 60s 没有内容自动发送ping , 不断开 , 客户端不回复 pong  ; 检测读 30s *6 心跳 , 没有内容动自动断开连接

                                //设置时间段内都没有数据读取了，那么就引发超时，然后关闭当前的channel --> 会自动断开
                                pipeline.addLast(new ReadTimeoutHandler(readTimeOut));
                                //控制数据输出的时候的超时，如果在设置时间段内都没有数据写了，那么就超时--> 会自动断开
                                //pipeline.addLast(new WriteTimeoutHandler(30 * 5));
                                /**
                                 * 空闲状态处理器
                                 * readerIdleTime : 空闲 ？ 读取时间 ， 自动发送心跳检测包
                                 * writerIdleTime ：空闲 ？ 写时间， 自动发送心跳包
                                 * allIdleTime： 空闲 ？ 读+写时间，自动发送心跳包
                                 */
                                pipeline.addLast(new IdleStateHandler(0, 60, 0, TimeUnit.SECONDS));
                                //重要：上下handler会互相关联，加入自定义心跳事件，对应上面函数IdleStateHandler-->会触发下面的handler-->HeatBeatHandler
                                pipeline.addLast(new HeatBeatObjectHandler());
                                //#endregion

                                //----------------------------------------



                            }
                            else if (serverType==SocketTypeEnum.CustomSocket) {
                                //#region -------------编解码---------------

                                //解码器 decoder 自带拆包功能
                                pipeline.addLast("decoder", new CustomMessageDecoder());
                                //编码器
                                pipeline.addLast("encoder", new CustomMessageEncoder());
                                //#endregion

                                //#region -------------心跳---------------

                                // (1)客户端  检测写心跳 , 30s 没有内容自动发送ping , 不断开 , 服务端不回复 pong  ; 检测读 60s *3 心跳 , 没有内容动自动断开连接
                                // (2)服务端  检测写心跳 , 60s 没有内容自动发送ping , 不断开 , 客户端不回复 pong  ; 检测读 30s *6 心跳 , 没有内容动自动断开连接

                                //设置时间段内都没有数据读取了，那么就引发超时，然后关闭当前的channel --> 会自动断开
                                pipeline.addLast(new ReadTimeoutHandler(readTimeOut));
                                //控制数据输出的时候的超时，如果在设置时间段内都没有数据写了，那么就超时--> 会自动断开
                                //pipeline.addLast(new WriteTimeoutHandler(30 * 5));
                                /**
                                 * 空闲状态处理器
                                 * readerIdleTime : 空闲 ？ 读取时间 ， 自动发送心跳检测包
                                 * writerIdleTime ：空闲 ？ 写时间， 自动发送心跳包
                                 * allIdleTime： 空闲 ？ 读+写时间，自动发送心跳包
                                 */
                                pipeline.addLast(new IdleStateHandler(0, 60, 0, TimeUnit.SECONDS));
                                //重要：上下handler会互相关联，加入自定义心跳事件，对应上面函数IdleStateHandler-->会触发下面的handler-->HeatBeatHandler
                                pipeline.addLast(new HeatBeatCustomHandler());
                                //#endregion

                                //----------------------------------------



                            }
                            //#endregion
                            //自定义pipleline
                            AddChannelHandler(pipeline);
                        }
                    });//给workGroup 对应处理程序
        } catch (Exception ex) {

        } finally {

        }
    }


    /**
     * 添加自定义 Handler
     * 可以重载， 并加上 super.AddChannelHandler(pipeline);
     */
    public abstract void AddChannelHandler(ChannelPipeline pipeline);

    /**
     * 添加自定义 Handler
     * @param childHandler

    public void AddChannelHandler(ChannelHandler childHandler) {
        if (bootstarp != null)
            bootstarp.childHandler(childHandler);

        //bootstarp.childHandler() 对应的是 workGroup
       // bootstarp.handler() 对应的是 boosGroup
    }
 */
    /**
     * 开启端口服务
     */
    public void Open() {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                IntSystem();
                Bind();
            }
        });
        t.start();
        
        

    }

    /**
     * 绑定启动端口
     */
    private void Bind()
    {
        try {
            for (int i = 0; i <1 ; i++) {
                Thread.sleep(2000);
                ChannelFuture channelFuture;
                //绑定一个端口并且同步，生成一个channelfutre 对象
                //启动服务器监听--》sync() 异步处理
                channelFuture = bootstarp.bind(port+i).sync();

                int finalI = i;
                channelFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {

                        if (future.isSuccess())
                            log.info("Netty Socket 服务端 启动  端口:" + (port+ finalI));
                        else
                            log.info("Netty Socket 服务端 失败  端口:" + (port+ finalI));
                    }

                });
            }


            //启动关闭通道监听 , 注意，此处会形成阻塞
          //channelFuture.channel().closeFuture().sync();
        }
        catch (Exception ex)
        {

        }
        finally {
            /*
            if (boosGroup != null)
                boosGroup.shutdownGracefully();
            if (workGroup != null)
                workGroup.shutdownGracefully();
            if (channelFuture!=null)
                channelFuture = null;

            if(isAutoConnected)
                Open();

             */
        }
    }

    /**
     * 停止服务端
     */
    private void Stop()
    {
        if (boosGroup != null)
            boosGroup.shutdownGracefully();
        if (workGroup != null)
            workGroup.shutdownGracefully();
        boosGroup=null;
        workGroup=null;
       // if (channelFuture!=null)
       //     channelFuture = null;
    }
    
    //#endregion

    //#region 消息发送

    /**
     * 群发所有消息(string)
     * @param msg
     */
    public  void SendAllStrMsg(String msg) {
        for (SocketChannelMapDTO dto :
                NettyRunTimeUtil.SocketChannelMap.values()) {
            NettyRunTimeUtil.sendStrMsg(msg,dto.getSocketChannel(),breakMark);
        }
    }


    /**
     * 群发所有消息（object类型)
     * @param msg
     */
    public  void SendAllObjMsg(Object msg) {
        for (SocketChannelMapDTO dto :
                NettyRunTimeUtil.SocketChannelMap.values()) {
            NettyRunTimeUtil.sendObjMsg(msg,dto.getSocketChannel());
        }
    }




    /**
     * 群发所有消息，排除id(string)
     * @param msg
     */
    public  void SendALLStrMsgNotId(String msg, String id) {

        for (SocketChannelMapDTO dto :
                NettyRunTimeUtil.SocketChannelMap.values()) {
            if (!dto.getId().equals(id))
                NettyRunTimeUtil.sendStrMsg(msg,dto.getSocketChannel(),breakMark);
        }
    }

    /**
     * 群发所有消息，排除id(Object)
     * @param msg
     * @param id
     */
    public  void SendALLObjMsgNotId(Object msg, String id) {
        for (SocketChannelMapDTO dto :
                NettyRunTimeUtil.SocketChannelMap.values()) {
            if (!dto.getId().equals(id))
                NettyRunTimeUtil.sendObjMsg(msg,dto.getSocketChannel());
        }
    }


    /**
     * 根据ID发送信息(String)
     * @param msg
     * @param id
     */
    public  void SendStrMsg(String msg, String id) {
        SocketChannelMapDTO dto = NettyRunTimeUtil.SocketChannelMap.get(id);
        if (dto != null)
            NettyRunTimeUtil.sendStrMsg(msg,dto.getSocketChannel(),breakMark);
    }

    /**
     * 根据ID发送信息(Object)
     * @param msg
     * @param id
     */
    public  void sendObjMsg(String msg, String id) {
        SocketChannelMapDTO dto = NettyRunTimeUtil.SocketChannelMap.get(id);
        if (dto != null)
            NettyRunTimeUtil.sendObjMsg(msg,dto.getSocketChannel());
    }

    /**
     * 发送消息到服务端
     *
     */
    public void sendByteMsg(byte[] bytes, String id) {
        SocketChannelMapDTO dto = NettyRunTimeUtil.SocketChannelMap.get(id);
        if (dto != null) {
            NettyRunTimeUtil.sendByteMsg(bytes,dto.getSocketChannel(),breakMark);
        }
    }

    /**
     * 发送文件
     * @param file
     */
    @Deprecated
    public void sendFileMsg(File file, String id)  {
        SocketChannelMapDTO dto = NettyRunTimeUtil.SocketChannelMap.get(id);
        if (dto == null || !dto.getSocketChannel().isActive())
            return;
        Channel  channel =dto.getSocketChannel();
        NettyRunTimeUtil.sendFileMsg(file,channel);
    }

    /**
     * 发送文件（object->CustomMessageProtocol)
     * @param file
     * @param id
     * @throws Exception
     */
    public void sendCustomFileMsg(File file, String id) throws Exception {
        SocketChannelMapDTO dto = NettyRunTimeUtil.SocketChannelMap.get(id);
        if (dto == null || !dto.getSocketChannel().isActive())
            return;
        Channel  channel =dto.getSocketChannel();
        NettyRunTimeUtil.sendCustomFileMsg(file,channel,log);
    }

    //#endregion

}
